﻿using System;
using System.Collections.Generic;
using System.Windows.Media;
using System.Windows.Controls;
using System.Windows;
using System.Diagnostics;
using System.Linq;
using System.Collections;

namespace FireFly
{
    /// <summary>
    /// Singlenton class which provides platform-dependent functionality for view model framework classes.
    /// </summary>
    public abstract class ViewModelService
    {
        static ViewModelService _instance = null;

        /// <summary>
        /// Singlenton instance. This property must be set before using of framework classes.
        /// </summary>
        public static ViewModelService Instance
        {
            get
            {
#if DEBUG
                if(_instance == null)
                    throw new ArgumentException("You must set ViewModelService instance before it will used in ViewModel interaction logic.");
#endif
                return _instance;
            }
            set
            {
                _instance = value;
            }
        }
        
        /// <summary>
        /// Make collection from set of view models and collections of view models. Processes collections show modes.
        /// </summary>
        /// <param name="items">Items to combine</param>
        public virtual ICollection CombineVisualChildren(params IViewModel[] items)
        {
            IList res = new ArrayList();

            // prepare list of elements.
            for (int i = 0; i < items.Length; i++)
			{
                if(items[i] is ICollectionViewModel)
                { // process collection
                    var col = items[i] as ICollectionViewModel;
                    if(col.ShowMode == CollectionShowMode.Transparent)
                    {
                        foreach(var item in col)
                        {
                            res.Add(item);
                        }
                        continue;
                    }else if(CollectionShowMode.HideEmptyCollection == (col.ShowMode & CollectionShowMode.HideEmptyCollection) &&
                        col.Count == 0)
                    {
                        continue;
                    }
                    res.Add(items[i]);
                }
                else //process view model
                    res.Add(items[i]);
            }

            // if the result is a single collections with HideSingleCollection mode - return it
            if(res.Count == 1 && res[0] is ICollectionViewModel)
            {
                var col = res[0] as ICollectionViewModel;
                if(CollectionShowMode.HideSingleCollection == (col.ShowMode & CollectionShowMode.HideSingleCollection))
                {
                    return col;
                }
            }
            
            return res;
        }

        #region icons
        /// <summary>
        /// Get icon for specified type
        /// </summary>
        public virtual ImageSource GetIcon(Type type)
        {
            return GetIcon(type.Name);
        }

        /// <summary>
        /// Returns icon by resource key
        /// </summary>
        public virtual ImageSource GetIcon(string key)
        {
            //you must override this method, if you want to use Icons of view models
            return null;
        }
        #endregion

        /// <summary>
        /// Extract string by resource key
        /// </summary>
        public virtual string GetString(string key)
        {
            return null;
            //return (string)Application.Current.TryFindResource(key) ?? "!!!" + key + "!!!";
        }

        #region menu
        /// <summary>
        /// Creates context menu for view model
        /// </summary>
        public virtual ContextMenu CreateContextMenu<TViewModel>(TViewModel vm, IEnumerable<IRelayCommand> commands) where TViewModel : IViewModel
        {
            if(commands == null)
                return null;

            ContextMenu menu = new ContextMenu();
            menu.Opened += new RoutedEventHandler(MenuOpenedEventhandler);

            foreach(var cmd in commands)
                menu.Items.Add(cmd.CreateMenuItem(menu, null));
            return menu;
        }

        /// <summary>
        /// Handler for MenuOpened event of context menus.
        /// </summary>
        protected virtual void MenuOpenedEventhandler(object sender, RoutedEventArgs e)
        {
            var items = GetNestedVisualChildren<MenuItem>(sender as DependencyObject);
            foreach(var item in items)
            {
                if(item.Command is IRelayCommand)
                {
                    (item.Command as IRelayCommand).UpdateCanExecute();
                }
            }
        }

        /// <summary>
        /// Creates menu item of commands of viewmodel
        /// </summary>
        /// <param name="viewmodel">view model which commands is used in menu creating</param>
        /// <param name="commands">set of commands for new menu items</param>
        /// <returns>...why not IList{MenuItem}? Because Separator is not an MenuItem instance...</returns>
        public virtual IList CreateMenuItems(IViewModel viewmodel, IEnumerable<IRelayCommand> commands)
        {
            var list = from cmd in commands
                       select cmd.CreateMenuItem(null, viewmodel) as object;

            return list.ToList();
        }
        #endregion

        /// <summary>
        /// Get all nested visual children of type T
        /// </summary>
        public static List<T> GetNestedVisualChildren<T>(DependencyObject obj) where T : DependencyObject
        {
            List<T> list = new List<T>();

            for(int i = 0, length = VisualTreeHelper.GetChildrenCount(obj); i < length; i++)
            {
                var child = VisualTreeHelper.GetChild(obj, i);
                Debug.WriteLine(string.Format("{0} {1}", i, child));
                if(child is T)
                    list.Add((T)child);
                list.AddRange(GetNestedVisualChildren<T>(child));
            }
            return list;
        }

        /// <summary>
        /// Evaluate relay command execution context at runtime
        /// </summary>
        /// <typeparam name="TViewModel">ViewModel type of the command</typeparam>
        /// <typeparam name="TContext">type of the UIContext</typeparam>
        public virtual CommandExecutionContext<TViewModel, TContext> GetCommandContext<TViewModel, TContext>(RelayCommand<TViewModel, TContext> command, object parameter) 
            where TViewModel: IViewModel
        {
            if(parameter == null)
                return null;

            CommandExecutionContext<TViewModel, TContext> context = null;
            
            //if parameter is MenuItem
//            if(SameTypeOrDerived<MenuItem>(parameter))  
            if(parameter is MenuItem)
            {
                var menu = GetContextMenu(parameter as MenuItem);
                if(menu != null)
                {
                    var target = (FrameworkElement)menu.PlacementTarget;
                    var data = target != null ? target.DataContext : null;
                    if(data is TViewModel)
                        context = new CommandExecutionContext<TViewModel, TContext>()
                        {
                            Sender = parameter as MenuItem,
                            ViewModel = (TViewModel)data,
                            PlacementTarget = target,
                        };
                    else
                        context = null;
                }
            }//if parameter is of type TViewModel
            else if(SameTypeOrDerived<TViewModel>(parameter))
            {
                context = new CommandExecutionContext<TViewModel, TContext>()
                {
                    ViewModel = (TViewModel)parameter,
                };
            }// if parameter is FrameworkElement
            else if( //SameTypeOrDerived<FrameworkElement>(parameter) && 
                (parameter is FrameworkElement &&  
                SameTypeOrDerived<TViewModel>(((FrameworkElement)parameter).DataContext)) ||
                (parameter is FrameworkContentElement &&
                SameTypeOrDerived<TViewModel>(((FrameworkContentElement)parameter).DataContext)))
            {
                var ctrl = (FrameworkContentElement)parameter;
                context = new CommandExecutionContext<TViewModel, TContext>()
                {
                    Sender = ctrl,
                    ViewModel = (TViewModel)(ctrl.DataContext)
                };
            }// if parameter is CommandContext
            else if(SameTypeOrDerived<CommandExecutionContext<TViewModel, TContext>>(parameter))
            {
                context = parameter as CommandExecutionContext<TViewModel, TContext>;
            }

                if(context != null)
            {
                context.AppContext = GetAppContext<TContext>();
                context.Command = command;
            }

            return context;
        }

        protected virtual ContextMenu GetContextMenu(MenuItem item)
        {
            DependencyObject parent = item;

            while(parent != null && !(parent is ContextMenu))
                parent = LogicalTreeHelper.GetParent(parent);

            return (ContextMenu)parent;
        }

        /// <summary>
        /// Check if objest is same type or derived from T
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="parameter"></param>
        /// <returns></returns>
        public static bool SameTypeOrDerived<T>(object parameter)
        {
            if(parameter == null)
                return false;
            
            return
                typeof(T) == parameter.GetType() ||
                parameter.GetType().IsSubclassOf(typeof(T));
        }

        /// <summary>
        /// You must override this method, if you want to use UI context in inherited from RelayCommand{T} commands
        /// </summary>
        /// <returns>result must be same type, as used in the </returns>
        public virtual TContext GetAppContext<TContext>()
        {
            return default(TContext);
        }

        /// <summary>
        /// Create new instance of view model.
        /// </summary>
        /// <remarks>Used in CollectionViewModel</remarks>
        public virtual TViewModel CreateViewModel<TModel, TViewModel>(TModel model, IHierarchicalViewModel parent, Func<TModel, TViewModel> constructor)
        {
            TViewModel vm = constructor(model);

            var hvm = vm as IHierarchicalViewModel;
            if(hvm!=null)
                hvm.Parent = parent;
            
            return vm;
        }

        /// <summary>
        /// Creates common SelectCommand for ViewModel{T} descendants
        /// </summary>
        public virtual IRelayCommand CreateStandartSelectCommand<T>()
        {
            return null;
        }

        /// <summary>
        /// Process exception (for example, show it to user in message box or log to file)
        /// </summary>
        /// <remarks>Used by RelayCommand.Execute() and RelayCommand.CanExecute()</remarks>
        public virtual void ProcessException(Exception err)
        {
            MessageBox.Show(err.Message);
            Debug.WriteLine(err.Message + "\n" + err.StackTrace);
        }
    }
}